#include "D3D11App.h"
#include "xnamath.h"
#include "d3dx11effect.h"
#include "D3DX11.h"

#include <fstream>
#include <vector>

#include "Camera.h"

struct Vertex
{
	XMFLOAT3 Pos;
	XMFLOAT4 Color;
};

template<typename T>
T Clamp(const T& x, const T& low, const T& high)
{
	return x < low ? low : (x > high ? high : x); 
}

struct MeshData
{
	std::vector<Vertex> Vertices;
	std::vector<UINT> Indices;
};


class MainApp : public D3D11App
{
public:
	MainApp(HINSTANCE hInstance);
	~MainApp();

	bool Init();
	void OnResize();
	void UpdateScene(float dt);
	void DrawScene();

	void OnMouseDown(WPARAM btnState, int x, int y);
	void OnMouseUp(WPARAM btnState, int x, int y);
	void OnMouseMove(WPARAM btnState, int x, int y);

private:
	void BuildGeometryBuffers();
	void BuildFX();
	void BuildVertexLayout();
	void CreateGrid(float width, float depth, UINT m, UINT n, MeshData& meshData);

private:
	ID3D11Buffer* mVB;
	ID3D11Buffer* mIB;

	ID3DX11Effect* mFX;
	ID3DX11EffectTechnique* mTech;
	ID3DX11EffectMatrixVariable* mfxWorldViewProj;

	ID3D11InputLayout* mInputLayout;

	ID3D11RasterizerState* mWireframeRS;

	XMFLOAT4X4 mWorld;
	XMFLOAT4X4 mView;
	XMFLOAT4X4 mProj;

	Camera mCam;

	POINT mLastMousePos;

	UINT mIndexCount;

	UINT mGridVertexOffset;
	UINT mShapeVertexOffset;

	UINT mGridIndexOffset;
	UINT mShapeIndexOffset;

	UINT mGridIndexCount;
	UINT mShapeIndexCount;
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd)
{
	MainApp theApp(hInstance);

	if(!theApp.Init())
		return 0;
	
	return theApp.Run();
}

MainApp::MainApp(HINSTANCE hInstance):D3D11App(hInstance), mVB(0), mIB(0), mfxWorldViewProj(0), mInputLayout(0),
										mIndexCount(0), mWireframeRS(0), mFX(0), mTech(0)
{
	mMainWndCaption = "TestingFLTExport";
	mLastMousePos.x = 0;
	mLastMousePos.y = 0;

	mCam.SetPosition(0.0f, 5.0f, -25.0f);

	XMMATRIX I = XMMatrixIdentity();
	XMStoreFloat4x4(&mView, I);
	XMStoreFloat4x4(&mProj, I);

	XMMATRIX T = XMMatrixTranslation(0.0f, 0.0f, 0.0f);
	//XMStoreFloat4x4(&mWorld, T);
	XMStoreFloat4x4(&mWorld, I);
}

MainApp::~MainApp()
{
}

bool MainApp::Init()
{
	if(!D3D11App::Init())
		return false;

	BuildGeometryBuffers();
	BuildFX();
	BuildVertexLayout();

	D3D11_RASTERIZER_DESC wireframeDesc;
	ZeroMemory(&wireframeDesc, sizeof(D3D11_RASTERIZER_DESC));

	wireframeDesc.FillMode = D3D11_FILL_WIREFRAME;
	wireframeDesc.CullMode = D3D11_CULL_NONE;
	wireframeDesc.FrontCounterClockwise = false;
	wireframeDesc.DepthClipEnable = true;

	md3dDevice->CreateRasterizerState(&wireframeDesc, &mWireframeRS);

	return true;
}

void MainApp::OnResize()
{
	D3D11App::OnResize();

	mCam.SetLens(0.25f*3.14159f, AspectRatio(), 1.0f, 1000.0f);

	//XMMATRIX P = XMMatrixPerspectiveFovLH(0.5f*3.14159, AspectRatio(), 1.0, 1000.0f);
	//XMStoreFloat4x4(&mProj, P);
}

void MainApp::UpdateScene(float dt)
{
	//
	// Control the camera.
	//
	if( GetAsyncKeyState('W') & 0x8000 )
		mCam.Walk(10.0f*dt);

	if( GetAsyncKeyState('S') & 0x8000 )
		mCam.Walk(-10.0f*dt);

	if( GetAsyncKeyState('A') & 0x8000 )
		mCam.Strafe(-10.0f*dt);

	if( GetAsyncKeyState('D') & 0x8000 )
		mCam.Strafe(10.0f*dt);

	if( GetAsyncKeyState('R') & 0x8000 )
		mCam.RotateY(0.5f*dt);

	if( GetAsyncKeyState('T') & 0x8000 )
		mCam.RotateY(-0.5f*dt);

	if( GetAsyncKeyState('E') & 0x8000 )
		mCam.Pitch(0.5f*dt);

	if( GetAsyncKeyState('Q') & 0x8000 )
		mCam.Pitch(-0.5f*dt);

	//XMVECTOR pos    = XMVectorSet(x, y, z, 1.0f);
	//XMVECTOR target = XMVectorZero();
	//XMVECTOR up     = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	//XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
	//XMStoreFloat4x4(&mView, V);
}

void MainApp::DrawScene()
{
	float blueColor[4] = {0.5f, 0.5f, 0.5f, 1.0f};
	md3dImmediateContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(blueColor));
	md3dImmediateContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

	md3dImmediateContext->IASetInputLayout(mInputLayout);
	md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	md3dImmediateContext->RSSetState(mWireframeRS);

	UINT stride = sizeof(Vertex);

	UINT offset = 0;
	md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB, &stride, &offset);
	md3dImmediateContext->IASetIndexBuffer(mIB, DXGI_FORMAT_R32_UINT, 0);

	mCam.UpdateViewMatrix();
 
	XMMATRIX view     = mCam.View();
	XMMATRIX proj     = mCam.Proj();
	XMMATRIX viewProj = mCam.ViewProj();

	XMMATRIX world = XMLoadFloat4x4(&mWorld);
	XMMATRIX worldViewProj = world * viewProj;

	mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));

	D3DX11_TECHNIQUE_DESC techDesc;
	mTech->GetDesc(&techDesc);
	for(UINT p = 0; p < techDesc.Passes; ++p)
	{
		mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
		md3dImmediateContext->DrawIndexed(mGridIndexCount, mGridIndexOffset, mGridVertexOffset);

		mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
		md3dImmediateContext->DrawIndexed(mShapeIndexCount, mShapeIndexOffset, mShapeVertexOffset);
	}

	mSwapChain->Present(0, 0);
}

void MainApp::BuildGeometryBuffers()
{
	//std::ifstream fin("FireEngine1.txt");

	//if(!fin)
	//{
	//	MessageBox(0, "FireEngine1.txt not found!", 0, 0);
	//	return;
	//}

	//UINT vertexCount = 0;
	//UINT triangleCount = 0;

	//std::string ignore;

	//fin >> ignore >> vertexCount;
	//fin >> ignore >> triangleCount;

	////fin >> ignore >> ignore >> ignore >> ignore;

	//XMFLOAT4 red(1.0f, 0.0f, 0.0f, 1.0f);

	//std::vector<Vertex> vertices(vertexCount);

	//float xig, yig, zig;

	//for(UINT i = 0; i < vertexCount; i++)
	//{
	//	fin >> vertices[i].Pos.x >> vertices[i].Pos.y >> vertices[i].Pos.z;
	//	vertices[i].Color = red;

	//	//fin >> xig >> yig >> zig;
	//}
	//
	////fin >> ignore;
	////fin >> ignore;
	////fin >> ignore;

	//mIndexCount = 3 * triangleCount;

	//std::vector<UINT> indices(mIndexCount);

	//for(UINT i = 0; i < triangleCount; i++)
	//{
	//	fin >> indices[i * 3 + 0] >> indices[i * 3 + 1] >> indices[i * 3 + 2];
	//}

	//fin.close();

	//D3D11_BUFFER_DESC vbd;
	//vbd.Usage = D3D11_USAGE_IMMUTABLE;
	//vbd.ByteWidth = sizeof(Vertex) * vertexCount;
	//vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	//vbd.CPUAccessFlags = 0;
	//vbd.MiscFlags = 0;
	//D3D11_SUBRESOURCE_DATA vertexInitData;
	//vertexInitData.pSysMem = &vertices[0];
	//md3dDevice->CreateBuffer(&vbd, &vertexInitData, &mVB);

	//D3D11_BUFFER_DESC ibd;
	//ibd.Usage = D3D11_USAGE_IMMUTABLE;
	//ibd.ByteWidth = sizeof(UINT) * mIndexCount;
	//ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	//ibd.CPUAccessFlags = 0;
	//ibd.MiscFlags = 0;
	//D3D11_SUBRESOURCE_DATA indexInitData;
	//indexInitData.pSysMem = &indices[0];
	//md3dDevice->CreateBuffer(&ibd, &indexInitData, &mIB);
	
	MeshData grid;
	CreateGrid(20.0f, 30.0f, 60, 40, grid);

	mGridVertexOffset  = 0;
	mShapeVertexOffset = grid.Vertices.size();

	UINT totalVertexCount = grid.Vertices.size() + 8;

	std::vector<Vertex> vertices(totalVertexCount);
	vertices.clear();

	UINT k = 0;
	for (int i = 0; i < grid.Vertices.size(); i++, k++)
	{
		vertices[k].Pos = grid.Vertices[i].Pos;
		vertices[k].Color = grid.Vertices[i].Color;
	}

	Vertex shapeVertices[] =
    {
		{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)  },
		{ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)  },
		{ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)  },
		{ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)  },
		{ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)  },
		{ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)  },
		{ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)  }
    };

	for (int i = 0; i < 8; i++, k++)
	{
		vertices[k].Pos = shapeVertices[i].Pos;
		vertices[k].Color = shapeVertices[i].Color;
	}

    D3D11_BUFFER_DESC vbd;
    vbd.Usage = D3D11_USAGE_IMMUTABLE;
    vbd.ByteWidth = sizeof(Vertex) * totalVertexCount;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    vbd.MiscFlags = 0;
	vbd.StructureByteStride = 0;
    D3D11_SUBRESOURCE_DATA vinitData;
    vinitData.pSysMem = &vertices[0];
    md3dDevice->CreateBuffer(&vbd, &vinitData, &mVB);

	//create index buffer
	mGridIndexOffset  = 0;
	mShapeIndexOffset = grid.Indices.size();

	UINT totalIndexCount = grid.Indices.size() + 36;
	mGridIndexCount = grid.Indices.size();
	mShapeIndexCount = 36;

	std::vector<UINT> indices(totalIndexCount);
	indices.clear();

	UINT j = 0;
	for(int i = 0; i < grid.Indices.size(); i++, j++)
	{
		indices[j] = grid.Indices[i];
	}

	UINT shapeIndices[] = {
		// front face
		0, 1, 2,
		0, 2, 3,

		// back face
		4, 6, 5,
		4, 7, 6,

		// left face
		4, 5, 1,
		4, 1, 0,

		// right face
		3, 2, 6,
		3, 6, 7,

		// top face
		1, 5, 6,
		1, 6, 2,

		// bottom face
		4, 0, 3, 
		4, 3, 7
	};

	for(int i = 0; i < 36; i++, j++)
	{
		indices[j] = shapeIndices[i];
	}

	D3D11_BUFFER_DESC ibd;
    ibd.Usage = D3D11_USAGE_IMMUTABLE;
    ibd.ByteWidth = sizeof(UINT) * totalIndexCount;
    ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    ibd.CPUAccessFlags = 0;
    ibd.MiscFlags = 0;
	ibd.StructureByteStride = 0;
    D3D11_SUBRESOURCE_DATA iinitData;
    iinitData.pSysMem = &indices[0];
    md3dDevice->CreateBuffer(&ibd, &iinitData, &mIB);
}

void MainApp::CreateGrid(float width, float depth, UINT m, UINT n, MeshData& meshData)
{
	UINT vertexCount = m*n;
	UINT faceCount   = (m-1)*(n-1)*2;

	//
	// Create the vertices.
	//

	float halfWidth = 0.5f*width;
	float halfDepth = 0.5f*depth;

	float dx = width / (n-1);
	float dz = depth / (m-1);

	float du = 1.0f / (n-1);
	float dv = 1.0f / (m-1);

	meshData.Vertices.resize(vertexCount);
	for(UINT i = 0; i < m; ++i)
	{
		float z = halfDepth - i*dz;
		for(UINT j = 0; j < n; ++j)
		{
			float x = -halfWidth + j*dx;

			meshData.Vertices[i*n+j].Pos = XMFLOAT3(x, 0.0f, z);
			meshData.Vertices[i*n+j].Color = XMFLOAT4(0.8f, 0.8f, 0.6f, 1.0f);
		}
	}
 
    //
	// Create the indices.
	//

	meshData.Indices.resize(faceCount*3); // 3 indices per face

	// Iterate over each quad and compute indices.
	UINT k = 0;
	for(UINT i = 0; i < m-1; ++i)
	{
		for(UINT j = 0; j < n-1; ++j)
		{
			meshData.Indices[k]   = i*n+j;
			meshData.Indices[k+1] = i*n+j+1;
			meshData.Indices[k+2] = (i+1)*n+j;

			meshData.Indices[k+3] = (i+1)*n+j;
			meshData.Indices[k+4] = i*n+j+1;
			meshData.Indices[k+5] = (i+1)*n+j+1;

			k += 6; // next quad
		}
	}
}

void MainApp::BuildVertexLayout()
{
	D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
		{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
	};

	D3DX11_PASS_DESC passDesc;
	mTech->GetPassByIndex(0)->GetDesc(&passDesc);
	md3dDevice->CreateInputLayout(vertexDesc, 2, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &mInputLayout);
}

void MainApp::BuildFX()
{
	std::ifstream fin("color.fxo", std::ios::binary);
	fin.seekg(0, std::ios_base::end);
	int size = (int)fin.tellg();
	fin.seekg(0, std::ios_base::beg);

	std::vector<char> compiledShader(size);
	fin.read(&compiledShader[0], size);
	fin.close();

	D3DX11CreateEffectFromMemory(&compiledShader[0], size, 0, md3dDevice, &mFX);

	mTech = mFX->GetTechniqueByName("ColorTech");
	mfxWorldViewProj = mFX->GetVariableByName("gWorldViewProj")->AsMatrix();
}

void MainApp::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;

	SetCapture(mhMainWnd);
}

void MainApp::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

void MainApp::OnMouseMove(WPARAM btnState, int x, int y)
{
	if( (btnState & MK_LBUTTON) != 0 )
	{
		// Make each pixel correspond to a quarter of a degree.
		float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));

		mCam.Pitch(dy);
		mCam.RotateY(dx);
	}

	mLastMousePos.x = x;
	mLastMousePos.y = y;
}